`timescale 1 ns / 1 ps

module tb;
    import espi_pkg::*;

`include "test_lib.v"

    // 100 MHz
    localparam CLK_PERIOD = 10;

    localparam MAX_OP_FREQ = 3'b000;
    
    localparam STATUS_REG_OFFSET     = 5'h00;
    localparam CONTROL_REG_OFFSET    = 5'h01;
    localparam PC_RXFIFO_REG_OFFSET  = 5'h02;
    localparam PC_TXFIFO_REG_OFFSET  = 5'h03;
    localparam NP_RXFIFO_REG_OFFSET  = 5'h04;
    localparam NP_TXFIFO_REG_OFFSET  = 5'h05;
    localparam ERROR_REG_OFFSET      = 5'h06;
    localparam ISR_REG_OFFSET        = 5'h07;
    localparam OOB_RXFIFO_REG_OFFSET = 5'h08;
    localparam OOB_TXFIFO_REG_OFFSET = 5'h09;

`define wr_reg     avmm_write_reg
`define rd_reg     avmm_read_reg

    //`define SIMULATION

    reg [7:0]               bfm_byte;
    reg [7:0]               bfm_data[];
    reg [7:0]               bfm_data_init = 8'h0;
    reg [7:0]               bfm_status[0:1];
    reg [7:0]               bytes_avail = 8'h0;
    reg [31:0]              write_data = 32'hAAAA_5555;
    reg [31:0]              read_data;
    reg [31:0]              exp_data;
    reg [31:0]              act_data = 32'h0;
    reg [31:0]              tb_wr_config = 32'h0;
    reg [7:0]               vwire_status = 8'h0;
    reg [7:0]               byte_count = 8'h0;
    reg                     completed;
    reg                     vw_enabled = 1'b0;

    bit [1:0]               io_mode;
    bit [2:0]               op_freq;

    parameter DEVICE_FAMILY              = "MAX 10 FPGA";
    parameter IO_MODE_RANGE              = 2'b11;
    parameter OPEN_DRAIN_ALERT_EN        = 1'b0;
    parameter MAX_FREQ_RANGE             = 3'b000;
    parameter CHANNEL_SUPPORT            = 8'b00000111;
    parameter MAX_PC_PAYLOAD_SIZE_RANGE  = 3'b001;
    parameter MAX_PC_READREQ_SIZE_RANGE  = 3'b001;
    parameter MAX_OOB_PAYLOAD_SIZE_RANGE = 3'b001;
    parameter MAX_VW_COUNT_SUPPORTED     = 6'b000111;

    logic         clk;
    logic         reset_n;
    logic         avmm_write;
    logic         avmm_read;
    logic [31:0]  avmm_writedata;
    logic [4:0]   avmm_address;
    logic [31:0]  avmm_readdata;
    logic         avmm_readdatavalid;
    // 
    // eSPI I/O Read/Write Short interface (Avalon Memory Mapped Master)
    logic         io_reset;
    logic         io_flush_fifo;
    logic [15:0]  io_address;
    logic         io_write;
    logic [7:0]   io_writedata;
    logic         io_read;
    logic [7:0]   io_readdata;
    logic         io_waitrequest;

    // interrupt
    logic         irq;
    logic         oob_rxfifo_avail;

    // espi protocol
    logic         espi_clk;
    logic         espi_reset_n;
    logic         espi_cs_n;
    logic [3:0]   espi_data_in;
    logic [3:0]   espi_data_out;
    logic         espi_alert_n;
    tri [3:0]     espi_io;

    // Virtual Wire external inputs & outputs
    logic [255:0] vw_irq; // Interrupt requests
    logic [255:0] vw_plti; // Platform-specific system inputs
    logic [255:0] vw_plto; // Platform-specific system outputs
    logic [511:0] vw_gpi; // General-Purpose inputs
    logic [511:0] vw_gpo; // General-Purpose outputs

    // Virtual Wire Outputs
    logic         slp_s3_n;
    logic         slp_s4_n;
    logic         slp_s5_n;
    logic         sus_stat_n;
    logic         pltrst_n;
    logic         oob_rst_warn;
    logic         host_rst_warn;
    logic         smiout_n;
    logic         nmiout_n;

    // Virtual Wire Inputs
    logic         slave_boot_load_done;
    logic         slave_boot_load_status;
    logic         oob_rst_ack;
    logic         wake_n;
    logic         pme_n;
    logic         sci_n;
    logic         smi_n;
    logic         rcin_n;
    logic         host_rst_ack;
    logic         rsmrst_n;
    logic         error_nonfatal;
    logic         error_fatal;

    // output FPGA_ESPI_BASE read only registers for access through avmm i/f
    logic [31:0]  device_id_reg;
    logic [31:0]  general_config_reg;
    logic [31:0]  channel0_config_reg;
    logic [31:0]  channel1_config_reg;
    logic [31:0]  channel2_config_reg;
    logic [31:0]  channel3_config_reg;


    espi_slave
      #(
        .DEVICE_FAMILY              (DEVICE_FAMILY),
        .IO_MODE_RANGE              (IO_MODE_RANGE),
        .OPEN_DRAIN_ALERT_EN        (OPEN_DRAIN_ALERT_EN),
        .MAX_FREQ_RANGE             (MAX_FREQ_RANGE),
        .CHANNEL_SUPPORT            (CHANNEL_SUPPORT),
        .MAX_PC_PAYLOAD_SIZE_RANGE  (MAX_PC_PAYLOAD_SIZE_RANGE),
        .MAX_PC_READREQ_SIZE_RANGE  (MAX_PC_READREQ_SIZE_RANGE),
        .MAX_OOB_PAYLOAD_SIZE_RANGE (MAX_OOB_PAYLOAD_SIZE_RANGE),
        .MAX_VW_COUNT_SUPPORTED     (MAX_VW_COUNT_SUPPORTED)
        )
    DUT
      (
       .clk                    (clk),
       .reset_n                (reset_n),
       .avmm_write             (avmm_write),
       .avmm_read              (avmm_read),
       .avmm_writedata         (avmm_writedata),
       .avmm_address           (avmm_address),
       .avmm_readdata          (avmm_readdata),
       .avmm_readdatavalid     (avmm_readdatavalid),
       .io_reset               (io_reset),
       .io_flush_fifo          (io_flush_fifo),
       .io_address             (io_address),
       .io_write               (io_write),
       .io_writedata           (io_writedata),
       .io_read                (io_read),
       .io_readdata            (io_readdata),
       .io_waitrequest         (io_waitrequest),
       .irq                    (irq),
       .oob_rxfifo_avail       (oob_rxfifo_avail),
       .espi_clk               (espi_clk),
       .espi_reset_n           (espi_reset_n),
       .espi_cs_n              (espi_cs_n),
       .espi_data_in           (espi_data_in),
       .espi_data_out          (espi_data_out),
       .espi_alert_n           (espi_alert_n),
       .vw_irq                 (vw_irq),
       .vw_plti                (vw_plti),
       .vw_plto                (vw_plto),
       .vw_gpi                 (vw_gpi),
       .vw_gpo                 (vw_gpo),
       .slp_s3_n               (slp_s3_n),
       .slp_s4_n               (slp_s4_n),
       .slp_s5_n               (slp_s5_n),
       .sus_stat_n             (sus_stat_n),
       .pltrst_n               (pltrst_n),
       .oob_rst_warn           (oob_rst_warn),
       .host_rst_warn          (host_rst_warn),
       .smiout_n               (smiout_n),
       .nmiout_n               (nmiout_n),
       .slave_boot_load_done   (slave_boot_load_done),
       .slave_boot_load_status (slave_boot_load_status),
       .oob_rst_ack            (oob_rst_ack),
       .wake_n                 (wake_n),
       .pme_n                  (pme_n),
       .sci_n                  (sci_n),
       .smi_n                  (smi_n),
       .rcin_n                 (rcin_n),
       .host_rst_ack           (host_rst_ack),
       .rsmrst_n               (rsmrst_n),
       .error_nonfatal         (error_nonfatal),
       .error_fatal            (error_fatal),
       .device_id_reg          (device_id_reg),
       .general_config_reg     (general_config_reg),
       .channel0_config_reg    (channel0_config_reg),
       .channel1_config_reg    (channel1_config_reg),
       .channel2_config_reg    (channel2_config_reg),
       .channel3_config_reg    (channel3_config_reg)
       );

    // Test stimulus
    initial begin
        espi_master_bfm.verbose = 1;
        espi_master_bfm.debug = 1;
        start_test_suite("Library:  ESPI I/F Test Suite");
        for (op_freq = 0; op_freq <= MAX_OP_FREQ; op_freq++) begin
            for (io_mode = 0; io_mode < 3; io_mode++) begin
                tb_reset();
                statusf("Run tests with I/O Mode = %1d and Op. Freq. = %1d", {argf(io_mode), argf(op_freq)});
                //`runtest("oob_test",  oob_test);
                //`runtest("vw_irq_test", vw_irq_test);
                //`runtest("vw_plti_test", vw_plti_test);
                `runtest("vw_gpi_test", vw_gpi_test);
                //`runtest("vw_plto_test", vw_plto_test);
                `runtest("vw_gpo_test", vw_gpo_test);
                //`runtest("put_iowr_short_test", put_iowr_short_test);
                //`runtest("put_iord_short_test", put_iord_short_test);
            end // for (io_mode = 0; io_mode < 3; io_mode++)
        end // for (op_freq = 0; op_freq <= MAX_OP_FREQ; op_freq++)
        end_test_suite ();
        $stop;
    end // initial begin

    // initial blocks
    initial begin
        clk = 0;
        #13;
        forever begin
            #(CLK_PERIOD/2) clk = ~clk;
        end
    end

    initial begin
        slave_boot_load_done   = 0;
        slave_boot_load_status = 0;
        oob_rst_ack            = 0;
        wake_n                 = 1;
        pme_n                  = 1;
        sci_n                  = 1;
        smi_n                  = 1;
        rcin_n                 = 1;
        host_rst_ack           = 0;
        rsmrst_n               = 1;
        error_nonfatal         = 0;
        error_fatal            = 0;
        vw_irq                 = 0;
        vw_plti                = 0;
        vw_gpi                 = 0;
        reset_n                = 0;
        repeat (10) @(posedge clk);

        reset_n = 1;
        @(posedge clk);
    end // initial begin

    // Initialize buffers for espi tests
    initial begin
        bfm_status[0] = 8'b0;
        bfm_status[1] = 8'b0;
    end

    reg [7:0] get_vw_return_data[];

    initial begin
        avmm_address    = 0;
        avmm_writedata  = 0;
        avmm_read       = 0;
        avmm_write      = 0;
    end

    espi_master_bfm espi_master_bfm
      (
       .espi_clk     (espi_clk    ),
       .espi_cs_n    (espi_cs_n   ),
       .espi_reset_n (espi_reset_n),
       .espi_io      (espi_io     ),
       .espi_alert_n (espi_alert_n)
       );

    assign espi_io      = espi_reset_n ? espi_data_out : 4'bzzzz;
    assign espi_data_in = espi_io;

    task avmm_read_reg(input [4:0] addr, output reg [31:0] rdata);
        $display("%t: %m: Address: %02h", $time, addr);
        @(posedge clk);
        avmm_address   = addr;
        avmm_read      = 1;
        avmm_writedata = 0;
        avmm_write     = 0;
        @(posedge clk);

        avmm_address = 0;
        avmm_read    = 0;
        wait (avmm_readdatavalid);
        @(posedge clk);

        rdata = avmm_readdata;
        $display("%t: %m: Data: %08h", $time, rdata);
    endtask // avmm_read_reg

    task avmm_write_reg(input [4:0] addr, input [31:0] wdata);
        $display("%t: %m: Address: %02h", $time, addr);
        $display("%t: %m: Data: %08h", $time, wdata);
        @(posedge clk);

        avmm_address   = addr;
        avmm_writedata = wdata;
        avmm_write     = 1;
        @(posedge clk);

        avmm_address   = 0;
        avmm_writedata = 0;
        avmm_write     = 0;
    endtask // avmm_write_reg

    logic [7:0] vw_group[];

    //////////////////////////////////////////////////////////////////////////////////////////////////////////
    // Verify that the EBG temperature can be read using OOB
    localparam PCH_OOB_SLV_ADR = 8'h02;
    localparam PCH_OOB_TEMP_CMD = 8'h01;
    localparam PCH_OOB_TEMP_BYTES = 8'h01;
    localparam ESPI_OOB_SLV_ADR = 8'hef;
    task oob_test();
        logic [7:0]  data[];
        logic [7:0]  rdata;
        logic [11:0] length;

        start_test("OOB PUT/GET Test");
        tb_reset();
        espi_config();
        deassert_pltrst_n();

        status("Write message to OOB TX FIFO");
        `wr_reg(OOB_TXFIFO_REG_OFFSET, OOB_MESSAGE);
        `wr_reg(OOB_TXFIFO_REG_OFFSET, 0);
        `wr_reg(OOB_TXFIFO_REG_OFFSET, 4);
        `wr_reg(OOB_TXFIFO_REG_OFFSET, PCH_OOB_SLV_ADR);
        `wr_reg(OOB_TXFIFO_REG_OFFSET, PCH_OOB_TEMP_CMD);
        `wr_reg(OOB_TXFIFO_REG_OFFSET, PCH_OOB_TEMP_BYTES);
        `wr_reg(OOB_TXFIFO_REG_OFFSET, ESPI_OOB_SLV_ADR | 1);

        status("Indicate the OOB TX FIFO has a message to send");
        `wr_reg(CONTROL_REG_OFFSET, 32'h04);

        status("Wait for ALERT# to assert");
        fork
            wait (!espi_alert_n);
            #1e6;
        join_any
        compare(0, espi_alert_n, "ALERT#");

        status("Read and verify that STATUS indicates an OOB message available");
        espi_master_bfm.get_status(bfm_status);
        compare(1, espi_alert_n, "ALERT#");
        compare('h8f, bfm_status[0], "ESPI STATUS[7:0]");
        compare('h03, bfm_status[1], "ESPI STATUS[15:0]");

        status("Send GET_OOB message");
        espi_master_bfm.get_oob(data);

        status("Verify received data is correct");
        compare(4, data.size(), "HEADER[1]");
        compare(PCH_OOB_SLV_ADR, data[0], "PCH OOB Slave Address");
        compare(PCH_OOB_TEMP_CMD, data[1], "Get Temperature Command");
        compare(PCH_OOB_TEMP_BYTES, data[2], "Byte Count");
        compare(ESPI_OOB_SLV_ADR, data[3], "FPGA OOB Slave Address");

        status("Send PUT_OOB message");
        data    = new[5];
        data[0] = ESPI_OOB_SLV_ADR;
        data[1] = PCH_OOB_TEMP_CMD;
        data[2] = PCH_OOB_TEMP_BYTES;
        data[3] = PCH_OOB_SLV_ADR | 1;
        data[4] = $urandom() % 256;
        espi_master_bfm.put_oob(data);
        fork
            begin
                status("Waiting for OOB Available");
                wait (oob_rxfifo_avail);
                status("OOB Available asserted");
            end

            begin
                #1e5;
                status ("OOB Available timeout");
            end
        join_any
        
        compare(1, oob_rxfifo_avail, "OOB MSI-X");
        #1e3;
        @(posedge clk);
        repeat (5) @(posedge clk);
        status("Verify data written to OOB RX FIFO is correct");
        `rd_reg(OOB_RXFIFO_REG_OFFSET, rdata);
        compare(6, rdata, "COMMAND = PUT_OOB");
        `rd_reg(OOB_RXFIFO_REG_OFFSET, rdata);
        compare(OOB_MESSAGE, rdata, "CYCLETYPE - OOB_MESSAGE");
        `rd_reg(OOB_RXFIFO_REG_OFFSET, rdata);
        compare(0, rdata, "HEADER[0] - TAG, LENGTH[11:8]");
        `rd_reg(OOB_RXFIFO_REG_OFFSET, rdata);
        compare(5, rdata, "HEADER[1] - LENGTH[7:0]");
        `rd_reg(OOB_RXFIFO_REG_OFFSET, rdata);
        compare(data[0], rdata, "ESPI_OOB_SLV_ADR");
        `rd_reg(OOB_RXFIFO_REG_OFFSET, rdata);
        compare(data[1], rdata, "PCH_OOB_TEMP_CMD");
        `rd_reg(OOB_RXFIFO_REG_OFFSET, rdata);
        compare(data[2], rdata, "PCH_BYTE_COUNT");
        `rd_reg(OOB_RXFIFO_REG_OFFSET, rdata);
        compare(data[3], rdata, "PCH_OOB_SLV_ADR");
        `rd_reg(OOB_RXFIFO_REG_OFFSET, rdata);
        compare(data[4], rdata, "PCH_TEMP");

        status("Write message to OOB TX FIFO again");
        `wr_reg(OOB_TXFIFO_REG_OFFSET, OOB_MESSAGE);
        `wr_reg(OOB_TXFIFO_REG_OFFSET, 0);
        `wr_reg(OOB_TXFIFO_REG_OFFSET, 4);
        `wr_reg(OOB_TXFIFO_REG_OFFSET, PCH_OOB_SLV_ADR);
        `wr_reg(OOB_TXFIFO_REG_OFFSET, PCH_OOB_TEMP_CMD);
        `wr_reg(OOB_TXFIFO_REG_OFFSET, PCH_OOB_TEMP_BYTES);
        `wr_reg(OOB_TXFIFO_REG_OFFSET, ESPI_OOB_SLV_ADR | 1);

        status("Indicate the OOB TX FIFO has a message to send again");
        `wr_reg(CONTROL_REG_OFFSET, 32'h04);

        status("Wait for ALERT# to assert");
        fork
            wait (!espi_alert_n);
            #1e5;
        join_any
        compare(0, espi_alert_n, "ALERT#");

        status("Read and verify that STATUS indicates an OOB message available again");
        espi_master_bfm.get_status(bfm_status);
        compare(1, espi_alert_n, "ALERT#");
        compare('h8f, bfm_status[0], "ESPI STATUS[7:0]");
        compare('h03, bfm_status[1], "ESPI STATUS[15:0]");

        status("Send GET_OOB message again");
        espi_master_bfm.get_oob(data);

        status("Verify received data is correct again");
        compare(4, data.size(), "HEADER[1]");
        compare(PCH_OOB_SLV_ADR, data[0], "PCH OOB Slave Address");
        compare(PCH_OOB_TEMP_CMD, data[1], "Get Temperature Command");
        compare(PCH_OOB_TEMP_BYTES, data[2], "Byte Count");
        compare(ESPI_OOB_SLV_ADR, data[3], "FPGA OOB Slave Address");

        status("Send PUT_OOB message again");
        data    = new[5];
        data[0] = ESPI_OOB_SLV_ADR;
        data[1] = PCH_OOB_TEMP_CMD;
        data[2] = PCH_OOB_TEMP_BYTES;
        data[3] = PCH_OOB_SLV_ADR | 1;
        data[4] = $urandom() % 256;
        espi_master_bfm.put_oob(data);
        fork
            begin
                status("Waiting for OOB Available again");
                wait (oob_rxfifo_avail);
                status("OOB Available asserted again");
            end

            begin
                #1e5;
                status ("OOB Available timeout");
            end
        join_any
        
        compare(1, oob_rxfifo_avail, "OOB MSI-X");
        #1e3;
        @(posedge clk);
        status("Verify data written to OOB RX FIFO is correct again");
        `rd_reg(OOB_RXFIFO_REG_OFFSET, rdata);
        compare(6, rdata, "COMMAND = PUT_OOB");
        `rd_reg(OOB_RXFIFO_REG_OFFSET, rdata);
        compare(OOB_MESSAGE, rdata, "CYCLETYPE - OOB_MESSAGE");
        `rd_reg(OOB_RXFIFO_REG_OFFSET, rdata);
        compare(0, rdata, "HEADER[0] - TAG, LENGTH[11:8]");
        `rd_reg(OOB_RXFIFO_REG_OFFSET, rdata);
        compare(5, rdata, "HEADER[1] - LENGTH[7:0]");
        `rd_reg(OOB_RXFIFO_REG_OFFSET, rdata);
        compare(data[0], rdata, "ESPI_OOB_SLV_ADR");
        `rd_reg(OOB_RXFIFO_REG_OFFSET, rdata);
        compare(data[1], rdata, "PCH_OOB_TEMP_CMD");
        `rd_reg(OOB_RXFIFO_REG_OFFSET, rdata);
        compare(data[2], rdata, "PCH_BYTE_COUNT");
        `rd_reg(OOB_RXFIFO_REG_OFFSET, rdata);
        compare(data[3], rdata, "PCH_OOB_SLV_ADR");
        `rd_reg(OOB_RXFIFO_REG_OFFSET, rdata);
        compare(data[4], rdata, "PCH_TEMP");

        end_test();
    endtask // oob_test


    task vw_irq_test();
        logic [7:0] rand_irq;
        start_test("VW IRQ Test");
        tb_reset();
        espi_config();
        status("Verify the assertion and deassertion of single interrupts.");
        vw_irq_check_single(0, 1);
        vw_irq_check_single(0, 0);
        vw_irq_check_single(255, 1);
        vw_irq_check_single(255, 0);
        rand_irq = $urandom_range(2, 253);
        for (int i=-1; i<2; i++) begin
            vw_irq_check_single(rand_irq+i, 1);
            vw_irq_check_single(rand_irq+i, 0);
        end
        end_test();
    endtask // vw_irq_test


    task vw_plti_test();
        logic [7:0] rand_plti;
        start_test("VW Platform Input Test");
        tb_reset();
        espi_config();
        status("Verify the assertion and deassertion of single platform inputs.");
        vw_plti_check_single(0, 1);
        vw_plti_check_single(0, 0);
        vw_plti_check_single(255, 1);
        vw_plti_check_single(255, 0);
        rand_plti = $urandom_range(3, 252);
        for (int i=-2; i<3; i++) begin
            vw_plti_check_single(rand_plti+i, 1);
            vw_plti_check_single(rand_plti+i, 0);
        end
        end_test();
    endtask // vw_plti_test


    task vw_gpi_test();
        logic [8:0] rand_gpi;
        start_test("VW General Purpose Input Test");
        tb_reset();
        espi_config();
        status("Verify the assertion and deassertion of single general purpose inputs.");
        vw_gpi_check_single(0, 1);
        vw_gpi_check_single(0, 0);
        vw_gpi_check_single(511, 1);
        vw_gpi_check_single(511, 0);
        rand_gpi = $urandom_range(3, 511-3);
        for (int i=-2; i<3; i++) begin
            vw_gpi_check_single(rand_gpi+i, 1);
            vw_gpi_check_single(rand_gpi+i, 0);
        end
        end_test();
    endtask // vw_gpi_test


    task vw_plto_test();
        logic [7:0] rand_plto;
        start_test("VW Platform Output Test");
        tb_reset();
        espi_config();
        status("Verify the assertion and deassertion of single platform outputs.");
        vw_plto_check_single(0, 1);
        vw_plto_check_single(0, 0);
        vw_plto_check_single(255, 1);
        vw_plto_check_single(255, 0);
        rand_plto = $urandom_range(3, 252);
        for (int i=-2; i<3; i++) begin
            vw_plto_check_single(rand_plto+i, 1);
            vw_plto_check_single(rand_plto+i, 0);
        end
        end_test();
    endtask // vw_plto_test


    task vw_gpo_test();
        logic [8:0] rand_gpo;
        start_test("VW General Purpose Output Test");
        tb_reset();
        espi_config();
        status("Verify the assertion and deassertion of single general purpose outputs.");
        vw_gpo_check_single(0, 1);
        vw_gpo_check_single(0, 0);
        vw_gpo_check_single(255, 1);
        vw_gpo_check_single(255, 0);
        rand_gpo = $urandom_range(3, 511-3);
        for (int i=-2; i<3; i++) begin
            vw_gpo_check_single(rand_gpo+i, 1);
            vw_gpo_check_single(rand_gpo+i, 0);
        end
        end_test();
    endtask // vw_gpo_test


    task put_iowr_short_test();
        logic [7:0] data[];
        logic [15:0] addr;
        int          n;
        start_test("PUT_IOWR_SHORT Test");
        tb_reset();
        espi_config();
        deassert_pltrst_n();
        for (int trial = 1; trial <= 10; trial++) begin
            addr = $urandom()%2**16;
            n    = $urandom_range(1,3);
            n    = n == 3 ? 4 : n;
            statusf("Writing %1d bytes to I/O address %04h", {argf(n), argf(addr)});
            data = new[n];
            for (int i = 0; i < n; i++) begin
                data[i] = $urandom()%2**8;
            end
            fork
                begin
                    espi_master_bfm.put_iowr_short(addr, data);
                end
                fork
                    begin
                        for (int i = 0; i < n; i++) begin
                            wait(io_write);
                            @(posedge clk);
                            compare((addr+i)&16'hffff, io_address, "I/O Address");
                            compare(data[i], io_writedata, "I/O Write Data");
                        end
                    end
                    begin
                        #1e6;
                        local_error_cnt++;
                        status("I/O Write TIMEOUT");
                    end
                join_any
            join
            disable fork;
        end // for (int trial = 1; trial <= 3; trial++)
        end_test();
    endtask // put_iowr_short_test
    

    task put_iord_short_test();
        logic [7:0] data[];
        logic [7:0] rdata[];
        logic [15:0] addr;
        int          n;
        start_test("PUT_IORD_SHORT Test");
        tb_reset();
        espi_config();
        deassert_pltrst_n();
        for (int trial = 1; trial <= 10; trial++) begin
            addr = $urandom()%2**16;
            n    = $urandom_range(1,3);
            n    = n == 3 ? 4 : n;
            statusf("Reading %1d bytes from I/O address %04h", {argf(n), argf(addr)});
            data  = new[n];
            rdata = new[n];
            for (int i = 0; i < n; i++) begin
                data[i] = $urandom()%2**8;
            end
            fork
                begin
                    espi_master_bfm.put_iord_short(completed, rdata, addr);
                end
                fork
                    begin
                        for (int i = 0; i < n; i++) begin
                            wait(io_read);
                            for (int waits = $urandom_range(0,7); waits > 0; waits--) begin
                                io_waitrequest = 1;
                                @(posedge clk);
                            end

                            io_waitrequest = 0;
                            io_readdata    = data[i];
                            @(posedge clk);

                            compare(addr + i, io_address, "I/O Address");
                        end // for (int i = 0; i < n; i++)
                    end // fork begin
                    begin
                        #1e6;
                        local_error_cnt++;
                        status("I/O Read TIMEOUT");
                    end
                join_any
            join

            disable fork;
            compare(1, completed, "I/O Read Completed");
            for (int i = 0; i < n; i++) begin
                compare(data[i], rdata[i], "I/O Read Data");
            end
        end // for (int trial = 1; trial <= 3; trial++)
        end_test();
    endtask // put_iord_short_test
    

    task automatic vw_irq_check_single(input [7:0] irq, input state);
        automatic logic [7:0] data[];
        automatic logic       prev_state = vw_irq[irq];
        status("Checking VWIRE IRQ");
        $display("\tSetting IRQ %1d to %b", irq, state);
        vw_irq[irq] = state;
        fork
            wait (!espi_alert_n);
            #1e5;
        join_any
        compare(state == prev_state, espi_alert_n, "eSPI ALERT#");
        if (!espi_alert_n) begin
            status("Verifying VWIRE content");
            espi_master_bfm.get_vwire(data);
            compare(2, data.size(), "GET_VWIRE bytes");
            compare(irq/128, data[0], "VWIRE Index");
            compare(irq%128|(state << 7), data[1], "VWIRE IRQ and state");
        end
    endtask // vw_irq_check_single

    
    task automatic vw_plti_check_single(input [7:0] plti, input state);
        automatic logic [7:0] data[];
        automatic logic       prev_state = vw_plti[plti];
        automatic logic [7:0] index;
        automatic logic [3:0] valid;
        automatic logic [3:0] value;
        automatic logic [1:0] pos;
        status("Checking VWIRE Platform Input");
        $display("\tSetting Platform Input %1d to %b", plti, state);
        vw_plti[plti] = state;
        fork
            wait (!espi_alert_n);
            #1e5;
        join_any
        compare(state == prev_state, espi_alert_n, "eSPI ALERT#");
        if (!espi_alert_n) begin
            status("Verifying VWIRE content");
            espi_master_bfm.get_vwire(data);
            index  = plti/4+64;
            pos    = plti%4;
            valid  = 1 << pos;
            value  = state << pos;
            compare(2, data.size(), "GET_VWIRE bytes");
            compare(index, data[0], "VWIRE Index");
            compare({valid, value}, data[1], "VWIRE Valid and Value");
        end // if (!espi_alert_n)
    endtask // vw_plti_check_single

    
    task automatic vw_gpi_check_single(input [8:0] gpi, input state);
        automatic logic [7:0] data[];
        automatic logic       prev_state = vw_gpi[gpi];
        automatic logic [7:0] index;
        automatic logic [3:0] valid;
        automatic logic [3:0] value;
        automatic logic [1:0] pos;
        status("Checking VWIRE General Purpose Input");
        $display("\tSetting General Purpose Input %1d to %b", gpi, state);
        vw_gpi[gpi] = state;
        fork
            wait (!espi_alert_n);
            #1e5;
        join_any
        compare(state == prev_state, espi_alert_n, "eSPI ALERT#");
        if (!espi_alert_n) begin
            status("Verifying VWIRE content");
            espi_master_bfm.get_vwire(data);
            $display("\tGET_VWIRE index %h returned: %h", data[0], data[1]);
	    $stop;
            index  = gpi/4+128;
            pos    = gpi%4;
            valid  = 1 << pos;
            value  = state << pos;
            compare(2, data.size(), "GET_VWIRE bytes");
            compare(index, data[0], "VWIRE Index");
            compare({valid, value}, data[1], "VWIRE Valid and Value");
        end // if (!espi_alert_n)
    endtask // vw_gpi_check_single

    
    task automatic vw_plto_check_single(input [7:0] plto, input state);
        automatic logic [7:0] data[];
        automatic logic       prev_state = vw_plto[plto];
        automatic logic [7:0] index;
        automatic logic [3:0] valid;
        automatic logic [3:0] value;
        automatic logic [1:0] pos;
        status("Checking VWIRE Platform Output");
        $display("\tSetting Platform Onput %1d to %b", plto, state);
        data    = new[2];
        index   = plto/4+64;
        pos     = plto%4;
        valid   = 1 << pos;
        value   = state << pos;
        data[0] = index;
        data[1] = {valid, value};
        espi_master_bfm.put_vwire(data);
        fork
            wait (vw_plto[plto] == state);
            #1e5;
        join_any
        compare(state, vw_plto[plto], "VWIRE Platform Output");
    endtask // vw_plto_check_single

    
    task automatic vw_gpo_check_single(input [8:0] gpo, input state);
        automatic logic [7:0] data[];
        automatic logic       prev_state = vw_gpo[gpo];
        automatic logic [7:0] index;
        automatic logic [3:0] valid;
        automatic logic [3:0] value;
        automatic logic [1:0] pos;
        status("Checking VWIRE General Purpose Output");
        $display("\tSetting General Purpose Onput %1d to %b", gpo, state);
        data    = new[2];
        index   = gpo/4+128;
        pos     = gpo%4;
        valid   = 1 << pos;
        value   = state << pos;
        data[0] = index;
        data[1] = {valid, value};
        espi_master_bfm.put_vwire(data);
        fork
            wait (vw_gpo[gpo] == state);
            #1e5;
        join_any
        compare(state, vw_gpo[gpo], "VWIRE General Purpose Output");
    endtask // vw_gpo_check_single

    
    task espi_reg_read(output [7:0] reg_data, input [15:0] addr);
        logic [7:0] data[];
        logic       completed;
        data = new[1];
        espi_master_bfm.put_iord_short(completed, data, addr);
        compare(1'b1, completed, "PUT_IORD_SHORT completed");
        reg_data = data[0];
    endtask // espi_reg_read

    task espi_reg_write(input [15:0] addr, input [7:0] reg_data);
        logic [7:0] data[];
        data    = new[1];
        data[0] = reg_data;
        espi_master_bfm.put_iowr_short(addr, data);
    endtask // espi_reg_write

    task espi_config();
        status("Configuring ESPI");
        espi_master_bfm.reset(); 

        espi_master_bfm.get_config(DEVICE_ID_OFFSET, tb_wr_config);
        exp_data = 32'h1;
        compare(exp_data, tb_wr_config, "Device ID");    

        espi_master_bfm.get_config(GENERAL_CONFIG_OFFSET, tb_wr_config);
        exp_data = 32'h0300_0007 | (MAX_OP_FREQ << 16);
        compare(exp_data, tb_wr_config, "General Configuration");    

        tb_wr_config = 
                       { 
                         1'b1,   // 31    - CRC Check enable
                         1'b0,   // 30    - Response modifier enabled: 0 slave must not append, 1 ok to append pc/vw/flash to GET_STATUS
                         1'b0,   // 29    - Reserved 
                         1'b0,   // 28    - Alert mode 0-> use IO[1] as alert, 0-> use alert pin
                         io_mode,  // 27:26 - IO mode selected -  single
                         2'b11,  // 25:24 - IO mode support (read only) - set to single/dual/quad
                         1'b0,   // 23    - open drain select, select not supported
                         op_freq, // 22:20 - select operating frequency
                         1'b1,   // 19    - HW init: 0 open drain alert not support, 1 supported 
                         3'b000, // 18:16 - Max freq supported
                         4'd0,   // 15:12 - Max wait state allowed
                         8'd0,   // 11:4  - Reserved
                         1'h1,   // 3     - flash supported (read only)
                         1'h1,   // 2     - oob suported (read only)
                         1'h1,   // 1     - vw suported (read only)
                         1'h1    // 0     - pc supported (read only)
                         };
        espi_master_bfm.set_config( GENERAL_CONFIG_OFFSET, tb_wr_config );
        espi_master_bfm.tb_io_mode = io_mode;
        espi_master_bfm.tb_op_freq = op_freq;
        #1;
        espi_master_bfm.get_config(CHANNEL_1_CONFIG_OFFSET, tb_wr_config);
        exp_data = 32'h700; // set to expected return data
        compare(exp_data, tb_wr_config, "VW configuration readback is incorrect");    

        tb_wr_config = 
                       {
                        10'b0,       // Reserved
                        6'b000111,   // Max Virtual wire count
                        2'b0,        // Reserved
                        6'b0,        // RO - max VW count supported
                        6'b0,        // Reserved
                        1'b0,        // RO - VW ready
                        1'b1         // VW enable
                        };
        espi_master_bfm.set_config(CHANNEL_1_CONFIG_OFFSET, tb_wr_config );

        espi_master_bfm.get_config(CHANNEL_2_CONFIG_OFFSET, tb_wr_config);
        exp_data = 32'h110; // set to expected return data
        compare(exp_data, tb_wr_config, "OOB configuration readback is incorrect");    

        tb_wr_config = 
                       {
                        20'b0,       // Reserved
                        3'b001,      // Max OOB payload size selected
                        1'b0,        // Reserved
                        3'b001,      // RO - max OOB payload size supported
                        2'b0,        // Reserved
                        1'b0,        // RO - OOB ready
                        1'b1         // OOB enable
                        };
        espi_master_bfm.set_config(CHANNEL_2_CONFIG_OFFSET, tb_wr_config );
        status("ESPI Configured");
    endtask // espi_config

    task deassert_pltrst_n();
        logic [7:0] data[];
        status("Deasserting PLTRST#");
        data    = new[2];
        data[0] = 8'h03;
        data[1] = 8'h22;
        espi_master_bfm.put_vwire(data);
        status("PLTRST# Deasserted");
    endtask // deassert_pltrst_n

    task tb_reset();
        @(posedge clk);

        slave_boot_load_done   = 0;
        slave_boot_load_status = 0;
        oob_rst_ack            = 0;
        wake_n                 = 1;
        pme_n                  = 1;
        sci_n                  = 1;
        smi_n                  = 1;
        rcin_n                 = 1;
        host_rst_ack           = 0;
        rsmrst_n               = 1;
        error_nonfatal         = 0;
        error_fatal            = 0;
        io_waitrequest         = 0;
        io_readdata            = 0;
        reset_n                = 0;
        repeat(50) @(posedge clk);

        reset_n = 1;
        @(posedge clk);
    endtask // tb_reset

endmodule // tb
